﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using System.Globalization;

using GeoRef;
using Storage.EntityModels;

namespace MapControl2
{
    /// <summary>
    /// MapControl2 server-side ajax-control. It implements the ScriptControl class and
    /// the IPostBackDataHandler interface, to communicate with the client-side javascript
    /// control.
    /// </summary>
    /// 
    public class LOAMap : ScriptControl, IPostBackDataHandler
    {
        public event EventHandler TripChanged;
        
        #region fields

        private string _googleKey;  // google api key
        private string _mapDiv;     //map div id

        // buffer of javascript statement executed at client side
        private List<string> _jsCmd;

        // WARN: this variable is consistent only after "Target" button click
        // or manual Sinchronize function ha call in client side
        private GeoTrip _geoTrip;

        // in map there is only one editable trip, so control send only one command to set editabletrip
        // index is neede to overwrite previus setEditabletrip command
        private int _editableTripCmdIndex = -1;

        // passenger info
        private GeoLoc _passDep;    // passenger departure location
        private double _passDepDev; // deviation radius from passenger departure
        private GeoLoc _passArr;    // passenger arrive location
        private double _passArrDev; // deviation radius from passenger arrive

        #endregion

        #region constructor

        public LOAMap()
        {
            _mapDiv = null;
            _jsCmd = new List<string>();
            _geoTrip = null;

            // chiave registrata per http://triploa.cli.di.unipi.it/
            // "ABQIAAAA6PuMZvkqJDY35l18oPnbjBRRz0caoklsKOxz4pKCS_kzV_zgVxQSDzfMTFThh6IZ-gGO2triLzhq-w";
            _googleKey = null;
        }

        #endregion

        #region properties
        // some properties that can be set either by c# code and in aspx page
        public string MapDiv
        {
            get
            {
                return _mapDiv;
            }
            set
            {
                _mapDiv = value;
            }
        }

        public string GoogleApiKey
        {
            set { _googleKey = value; }
            get { return _googleKey; }
        }

        public int ZoomLevel
        {
            set
            {
                string jsCmd = "this._map.setZoom(" + value + ");";
                _jsCmd.Add(jsCmd);
            }
        }

        public GeoLoc MapCenter
        {
            set
            {
                string jsCmd = "this._map.setCenter(new google.maps.LatLng(" + 
                            value.Lat + "," + value.Lng + "));";
                _jsCmd.Add(jsCmd);
            }
        }

        public bool Editable
        {
            set 
            {
                string jsCmd;
                if(value)
                    jsCmd = "this._map.setEditable(true);";
                else
                    jsCmd = "this._map.setEditable(false);";
                _jsCmd.Add(jsCmd);
            }
        }

        public GeoTrip EditableTrip
        {
            get { return _geoTrip; }
            set
            {
                _geoTrip = value;
                
                string jsCmd = "if(this._map._isEditable)" +
                                    "this._tripsManager.createTripFromGeoTrip(" +
                                    GeoSerializer.ToJsonString(typeof(GeoTrip), value) +
                                    ", true, Function.createDelegate(this, this._onCreateTripCallback) );";
                
                if (_editableTripCmdIndex != -1)
                {
                    // user are already set editble trip in this transaction, so its statement
                    // must be overwrite
                    _jsCmd[_editableTripCmdIndex] = jsCmd;
                }
                else
                {
                    _editableTripCmdIndex = _jsCmd.Count;
                    _jsCmd.Add(jsCmd);
                }
            }
        }

        public List<Users_VehicleDetails> Vehicles
        {
            set
            {
                string jsonVehicles = "[";
                for (int i = 0; i < value.Count - 1; i++)
                {
                    jsonVehicles += "{'Id':" + value[i].Id +
                                     ", 'Type':'" + value[i].VehicleDetails.Model +
                                     "', 'Class':" + (int)value[i].VehicleDetails.VehicleClass + "},";
                }

                if (value.Count > 0)
                {
                    jsonVehicles += "{'Id':" + value[value.Count - 1].Id +
                                     ", 'Type':'" + value[value.Count - 1].VehicleDetails.Model +
                                     "', 'Class':" + (int)value[value.Count - 1].VehicleDetails.VehicleClass + "}";
                }
                jsonVehicles += "]";

                string jsCmd = "this._map._listVehicles = " + jsonVehicles + ";" +
                                "this._map._menu._setVehicleMenu(this._map._listVehicles);";
                               
                _jsCmd.Add(jsCmd);
            }
        }

        public string TargetControlIDs
        {
            set 
            {
                string jsCmd = "this._map._targetControlIDs = '" + value + "';";
                _jsCmd.Add(jsCmd); 
            }
        }

        public GeoLoc PassengerDeparture 
        {
            get { return _passDep; }
        }

        public double PassengerDepartureRadius
        {
            get { return _passDepDev; }
        }

        public GeoLoc PassengerArrive
        {
            get { return _passArr; }
        }

        public double PassengerArriveRadius
        {
            get { return _passArrDev; }
        }

        protected string editableHF
        { get { return "__" + ClientID + "_HF_EDITABLE"; } }

        protected string passengerHF
        { get { return "__" + ClientID + "_HF_PASSENGER"; } }

        #endregion

        #region Methods

        //draws a non-editable trip on the map (once downloaded)
        public void DrawTrip(GeoTrip trip2Draw)
        {
            string jsCmd = "this._tripsManager.createTripFromGeoTrip("+
                            GeoSerializer.ToJsonString(typeof(GeoTrip), trip2Draw) +
                            ",false,Function.createDelegate(this, this._onCreateTripCallback));";
            _jsCmd.Add(jsCmd);
        }

        public void ClearMap()
        {
            string jsCmd = "this._map.clearUneditableTrips();" +
                           "this._map.clearEditableTrip();";
            _jsCmd.Add(jsCmd);
        }

        /// <summary>
        /// Insert into map an icon to show an help string.
        /// </summary>
        /// <param name="info">The help string</param>
        /// <remarks>info string cannot contain char ' as simply char it have to replaced with\'</remarks>
        public void SetHelp(string info)
        {
            string jsCmd = "this.setHelp('" + info + "');";

            _jsCmd.Add(jsCmd);
        }

        /// <summary>
        /// Set Passenger location allows edit the editable trip to accept passenger into trip.
        /// If map is editable passenger locations can be moved only in their sensible area and editable trip can
        /// be modified to take in the passenger.
        /// If map is not editable, the user is the passenger who can move its location and the trip is not
        /// editable.
        /// </summary>
        /// <param name="passId">Passenger Id</param>
        /// <param name="passDep">Location where passenger is getting in.</param>
        /// <param name="depDev">Deviation radius which passenger could be moved</param>
        public void SetPassengerDeparture(int passId, GeoLoc passDep, double depDev)
        {
            string jsCmd = "this._map.setPassengerDeparture(" + passId + "," +
                            GeoSerializer.ToJsonString(typeof(GeoLoc), passDep) + "," + depDev.ToString(CultureInfo.InvariantCulture) + ");";
            _jsCmd.Add(jsCmd);
        }

        /// <summary>
        /// Set Passenger location allowing edit the editable trip to accept passenger into trip.
        /// If map is editable passenger locations can be moved only in its sensible area and editable trip can
        /// be modified to gettin in the passenger.
        /// If map is not editable, the user is the passenger who can move its location.
        /// </summary>
        /// <param name="passId">Passenger Id</param>
        /// <param name="passArr">Location were passenger is getting off.</param>
        /// <param name="arrDev">Deviation radius of arrive which passenger could be moved</param>
        public void SetPassengerArrive(int passId, GeoLoc passArr, double arrDev)
        {
            string jsCmd = "this._map.setPassengerArrive(" + passId + "," +
                            GeoSerializer.ToJsonString(typeof(GeoLoc), passArr) + "," + arrDev.ToString(CultureInfo.InvariantCulture) + ");";
            _jsCmd.Add(jsCmd);
        }


        /// <summary>
        /// Set Passenger location allowing edit the editable trip to accept passenger into trip.
        /// If map is editable passenger locations can be moved only in its sensible area and editable trip can
        /// be modified to gettin in the passenger.
        /// If map is not editable, the user is the passenger who can move its location.
        /// </summary>
        /// <param name="passId">Passenger Id</param>
        /// <param name="passDep">Location were passenger is getting in.</param>
        /// <param name="depDev">Deviation radius which passenger could be moved</param>
        /// <param name="passArr">Location were passenger is getting off.</param>
        /// <param name="arrDev">Deviation radius of arrive which passenger could be moved</param>
        public void SetPassenger(int passId, GeoLoc passDep, double depDev, GeoLoc passArr, double arrDev)
        {
            string jsCmd = "this._map.setPassengerPositions(" + passId + "," +
                            GeoSerializer.ToJsonString(typeof(GeoLoc), passDep) + "," + depDev.ToString(CultureInfo.InvariantCulture) + "," +
                            GeoSerializer.ToJsonString(typeof(GeoLoc), passArr) + "," + arrDev.ToString(CultureInfo.InvariantCulture) + ");";
            _jsCmd.Add(jsCmd);
        }

        public void ChangePassengerDepartureRadius(double radiusKm)
        {
            string jsCmd = "if(this._map._passengerDep != null)" +
                            "this._map._passengerDep.setRadiusKm(" +
                            radiusKm.ToString(CultureInfo.InvariantCulture) + ");";
            _jsCmd.Add(jsCmd);
        }

        public void ChangePassengerArriveRadius(double radiusKm)
        {
            string jsCmd = "if(this._map._passengerArr != null)" +
                            "this._map._passengerArr.setRadiusKm(" +
                            radiusKm.ToString(CultureInfo.InvariantCulture) + ");";
            _jsCmd.Add(jsCmd);
        }

        public void ChangePassengerDepartureCenter(GeoLoc center)
        {
            string jsCmd = "if(this._map._passengerDep != null)" +
                            "this._map._passengerDep.setCenterFromGeoloc(" +
                            GeoSerializer.ToJsonString(typeof(GeoLoc), center) + ");";
            _jsCmd.Add(jsCmd);
        }

        public void ChangePassengerArriveCenter(GeoLoc center)
        {
            string jsCmd = "if(this._map._passengerArr != null)" +
                            "this._map._passengerArr.setCenterFromGeoloc(" +
                            GeoSerializer.ToJsonString(typeof(GeoLoc), center) + ");";
            _jsCmd.Add(jsCmd);
        }
        /// <summary>
        /// Allow to run some javascript code in client-side.
        /// <remarks>The javascript code will be invoked into loaMap object scope.
        /// For example invoke a AAAMap function would be something like:
        /// this._map.setCenter(...);
        /// </remarks>
        /// </summary>
        public void InvokeJSCommand(string jsCode)
        {
            _jsCmd.Add(jsCode);
        }


        #endregion

        #region script_control_implementation

        /// <summary>
        /// Invoked by the ajax framework when this control is instantiated. It defines the javascript
        /// variables and handlers that must be generated by the framework at client-side.
        /// </summary>
        /// <returns>
        /// An IEnumerable implementation of ScriptDescriptor objects defining the initial status of
        /// the javascript control associated with this ScriptControl.
        /// </returns>
        protected override IEnumerable<ScriptDescriptor>
                GetScriptDescriptors()
        {
            ScriptControlDescriptor descriptor = new ScriptControlDescriptor("MapControl2.LOAMap", this.ClientID);
            
            descriptor.AddProperty("_mapDiv", _mapDiv);
            descriptor.AddProperty("_cmdToEval",_jsCmd);
            
            yield return descriptor;
        }

        /// <summary>
        /// Invoked by the ajax framework when this control is instantiated. It defines which
        /// javascript files must be downloaded with the control.
        /// </summary>
        /// <returns>
        /// An IEnumerable implementation of ScriptReference objects. Each ScriptReference tells
        /// the framework to download its associated javascript file.
        /// </returns>
        protected override IEnumerable<ScriptReference>
                GetScriptReferences()
        {
            if (_googleKey == null)
                throw new Exception("LOAMap control needs Google Api Key setted!");
            
            // chiave registratahttp://triploa.cli.di.unipi.it/secure
            // to do: cambiare key a seconda lel'url della pagina in modo da "risparmiare" accessi
            //mapsKey = "ABQIAAAA6PuMZvkqJDY35l18oPnbjBROw3i69wAs-p_LY1Yv_tdASuaZcRTrJ_a2fllxbcY4joJ8_cQVO2G_EQ";
                   
            ScriptReference[] refs = 
            {
                //api references
                new ScriptReference("http://www.google.com/jsapi?key=" + _googleKey),

                // core classes
                new ScriptReference("MapControl2.LOAMap.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAMarker.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAATripsManager.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAANetUtils.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAUtils.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAARoute.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAATrip.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAMap.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAMenu.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAPassengerMarker.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAGeoCodingMenu.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.AAAVirtualMap.js", this.GetType().Assembly.FullName),

                // visitors
                new ScriptReference("MapControl2.jsapi.visitors.AAAJsonSerializerVisitor.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.visitors.AAAMarkersFormatterVisitor.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.visitors.AAAVisitorFactory.js", this.GetType().Assembly.FullName),

                // libs
                new ScriptReference("MapControl2.jsapi.libs.animate.js", this.GetType().Assembly.FullName),
                new ScriptReference("MapControl2.jsapi.libs.googleext.js", this.GetType().Assembly.FullName)
            };
            return refs;
        }

        #endregion

        #region IPostBackDataHandler implementation

        /// <summary>
        /// Invoked by the asp.net runtime environment. It registers this IPostBackDataHandler
        /// implementation with the Page object.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            if (Page != null)
            {
                Page.RegisterRequiresPostBack(this);
            }
        }

        /// <summary>
        /// It intercepts the OnPreRender event and registers some hidden fields with
        /// the page client script.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            
            base.OnPreRender(e);
            if (Page != null)
            {
                Page.ClientScript.RegisterHiddenField(editableHF, "");
                Page.ClientScript.RegisterHiddenField(passengerHF, "");
            }
        }

        /// <summary>
        /// Invoked by the asp.net runtime environment when post data has been sent from the
        /// client to the server. It retrieves sent info about passengers and the editable trip 
        /// and, if necessary, raises the TripChanged event.
        /// </summary>
        /// <param name="postDataKey"></param>
        /// <param name="postCollection"></param>
        /// <returns></returns>
        bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)
        {
            // here it came from client, control is already present in client side
            // so inizialization and old request have to be resetted
            _jsCmd.Clear();
            _passDep = null;
            _passDepDev = -1;
            _passArr = null;
            _passArrDev = -1;
            _editableTripCmdIndex = -1;

            // get passenger position send by client
            string value = postCollection[passengerHF];
            
            if (value != null && value != "")
            {
                string[] res = value.Split('\n');
                if (res.Length == 4)
                {
                    _passDep = (GeoLoc)GeoSerializer.FromJsonString(typeof(GeoLoc), res[0]);
                    _passDepDev = double.Parse(res[1], CultureInfo.InvariantCulture);
                    _passArr = (GeoLoc)GeoSerializer.FromJsonString(typeof(GeoLoc), res[2]);
                    _passArrDev = double.Parse(res[3], CultureInfo.InvariantCulture);
                }
            }

            // get editable trip serialization send by client
            value = postCollection[editableHF];
            //value = value.Replace("\\", "\\\\");
            if (value != null && value != "")
            {
                GeoTrip newValue = null;
                try
                {
                    newValue = (GeoTrip)GeoSerializer.FromJsonString(typeof(GeoTrip), value);
                }
                catch (Exception)
                {
                    newValue = null;
                }

                // If there is a change, raise a change event.
                if (newValue != EditableTrip)
                {
                    EditableTrip = newValue;
                    if (TripChanged != null)
                    {
                        TripChanged(this, EventArgs.Empty);
                    }
                    return true;
                }
            }
            return false;
        }
        
        void IPostBackDataHandler.RaisePostDataChangedEvent()
        {
            // There was a change,  so raise any events.
            if (TripChanged != null)
            {
                TripChanged(this, EventArgs.Empty);
            }
        }
        
        #endregion
    }
}
